{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Implements a Siamese/Y-Network using Functional API\n",
    "\n",
    "~99.4% test accuracy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.6/site-packages/h5py/__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.\n",
      "  from ._conv import register_converters as _register_converters\n",
      "Using TensorFlow backend.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "__________________________________________________________________________________________________\n",
      "Layer (type)                    Output Shape         Param #     Connected to                     \n",
      "==================================================================================================\n",
      "input_1 (InputLayer)            (None, 28, 28, 1)    0                                            \n",
      "__________________________________________________________________________________________________\n",
      "input_2 (InputLayer)            (None, 28, 28, 1)    0                                            \n",
      "__________________________________________________________________________________________________\n",
      "conv2d_1 (Conv2D)               (None, 28, 28, 32)   320         input_1[0][0]                    \n",
      "__________________________________________________________________________________________________\n",
      "conv2d_4 (Conv2D)               (None, 28, 28, 32)   320         input_2[0][0]                    \n",
      "__________________________________________________________________________________________________\n",
      "dropout_1 (Dropout)             (None, 28, 28, 32)   0           conv2d_1[0][0]                   \n",
      "__________________________________________________________________________________________________\n",
      "dropout_4 (Dropout)             (None, 28, 28, 32)   0           conv2d_4[0][0]                   \n",
      "__________________________________________________________________________________________________\n",
      "max_pooling2d_1 (MaxPooling2D)  (None, 14, 14, 32)   0           dropout_1[0][0]                  \n",
      "__________________________________________________________________________________________________\n",
      "max_pooling2d_4 (MaxPooling2D)  (None, 14, 14, 32)   0           dropout_4[0][0]                  \n",
      "__________________________________________________________________________________________________\n",
      "conv2d_2 (Conv2D)               (None, 14, 14, 64)   18496       max_pooling2d_1[0][0]            \n",
      "__________________________________________________________________________________________________\n",
      "conv2d_5 (Conv2D)               (None, 14, 14, 64)   18496       max_pooling2d_4[0][0]            \n",
      "__________________________________________________________________________________________________\n",
      "dropout_2 (Dropout)             (None, 14, 14, 64)   0           conv2d_2[0][0]                   \n",
      "__________________________________________________________________________________________________\n",
      "dropout_5 (Dropout)             (None, 14, 14, 64)   0           conv2d_5[0][0]                   \n",
      "__________________________________________________________________________________________________\n",
      "max_pooling2d_2 (MaxPooling2D)  (None, 7, 7, 64)     0           dropout_2[0][0]                  \n",
      "__________________________________________________________________________________________________\n",
      "max_pooling2d_5 (MaxPooling2D)  (None, 7, 7, 64)     0           dropout_5[0][0]                  \n",
      "__________________________________________________________________________________________________\n",
      "conv2d_3 (Conv2D)               (None, 7, 7, 128)    73856       max_pooling2d_2[0][0]            \n",
      "__________________________________________________________________________________________________\n",
      "conv2d_6 (Conv2D)               (None, 7, 7, 128)    73856       max_pooling2d_5[0][0]            \n",
      "__________________________________________________________________________________________________\n",
      "dropout_3 (Dropout)             (None, 7, 7, 128)    0           conv2d_3[0][0]                   \n",
      "__________________________________________________________________________________________________\n",
      "dropout_6 (Dropout)             (None, 7, 7, 128)    0           conv2d_6[0][0]                   \n",
      "__________________________________________________________________________________________________\n",
      "max_pooling2d_3 (MaxPooling2D)  (None, 3, 3, 128)    0           dropout_3[0][0]                  \n",
      "__________________________________________________________________________________________________\n",
      "max_pooling2d_6 (MaxPooling2D)  (None, 3, 3, 128)    0           dropout_6[0][0]                  \n",
      "__________________________________________________________________________________________________\n",
      "concatenate_1 (Concatenate)     (None, 3, 3, 256)    0           max_pooling2d_3[0][0]            \n",
      "                                                                 max_pooling2d_6[0][0]            \n",
      "__________________________________________________________________________________________________\n",
      "flatten_1 (Flatten)             (None, 2304)         0           concatenate_1[0][0]              \n",
      "__________________________________________________________________________________________________\n",
      "dropout_7 (Dropout)             (None, 2304)         0           flatten_1[0][0]                  \n",
      "__________________________________________________________________________________________________\n",
      "dense_1 (Dense)                 (None, 10)           23050       dropout_7[0][0]                  \n",
      "==================================================================================================\n",
      "Total params: 208,394\n",
      "Trainable params: 208,394\n",
      "Non-trainable params: 0\n",
      "__________________________________________________________________________________________________\n",
      "Train on 60000 samples, validate on 10000 samples\n",
      "Epoch 1/20\n",
      "60000/60000 [==============================] - 351s 6ms/step - loss: 0.1769 - acc: 0.9435 - val_loss: 0.1412 - val_acc: 0.9904\n",
      "Epoch 2/20\n",
      "60000/60000 [==============================] - 361s 6ms/step - loss: 0.0664 - acc: 0.9795 - val_loss: 0.0923 - val_acc: 0.9903\n",
      "Epoch 3/20\n",
      "60000/60000 [==============================] - 359s 6ms/step - loss: 0.0528 - acc: 0.9835 - val_loss: 0.0772 - val_acc: 0.9908\n",
      "Epoch 4/20\n",
      "60000/60000 [==============================] - 286s 5ms/step - loss: 0.0471 - acc: 0.9854 - val_loss: 0.0836 - val_acc: 0.9930\n",
      "Epoch 5/20\n",
      "60000/60000 [==============================] - 244s 4ms/step - loss: 0.0429 - acc: 0.9861 - val_loss: 0.0736 - val_acc: 0.9931\n",
      "Epoch 6/20\n",
      "60000/60000 [==============================] - 242s 4ms/step - loss: 0.0393 - acc: 0.9878 - val_loss: 0.0507 - val_acc: 0.9931\n",
      "Epoch 7/20\n",
      "60000/60000 [==============================] - 183s 3ms/step - loss: 0.0364 - acc: 0.9883 - val_loss: 0.0434 - val_acc: 0.9934\n",
      "Epoch 8/20\n",
      "60000/60000 [==============================] - 141s 2ms/step - loss: 0.0364 - acc: 0.9890 - val_loss: 0.0471 - val_acc: 0.9931\n",
      "Epoch 9/20\n",
      "60000/60000 [==============================] - 139s 2ms/step - loss: 0.0358 - acc: 0.9892 - val_loss: 0.0384 - val_acc: 0.9938\n",
      "Epoch 10/20\n",
      "60000/60000 [==============================] - 138s 2ms/step - loss: 0.0342 - acc: 0.9891 - val_loss: 0.0500 - val_acc: 0.9915\n",
      "Epoch 11/20\n",
      "60000/60000 [==============================] - 138s 2ms/step - loss: 0.0327 - acc: 0.9895 - val_loss: 0.0328 - val_acc: 0.9919\n",
      "Epoch 12/20\n",
      "60000/60000 [==============================] - 137s 2ms/step - loss: 0.0328 - acc: 0.9896 - val_loss: 0.0364 - val_acc: 0.9928\n",
      "Epoch 13/20\n",
      "60000/60000 [==============================] - 139s 2ms/step - loss: 0.0334 - acc: 0.9898 - val_loss: 0.0322 - val_acc: 0.9938\n",
      "Epoch 14/20\n",
      "60000/60000 [==============================] - 143s 2ms/step - loss: 0.0306 - acc: 0.9908 - val_loss: 0.0346 - val_acc: 0.9942\n",
      "Epoch 15/20\n",
      "60000/60000 [==============================] - 180s 3ms/step - loss: 0.0319 - acc: 0.9903 - val_loss: 0.0417 - val_acc: 0.9942\n",
      "Epoch 16/20\n",
      "60000/60000 [==============================] - 148s 2ms/step - loss: 0.0306 - acc: 0.9906 - val_loss: 0.0308 - val_acc: 0.9930\n",
      "Epoch 17/20\n",
      "60000/60000 [==============================] - 149s 2ms/step - loss: 0.0299 - acc: 0.9906 - val_loss: 0.0300 - val_acc: 0.9945\n",
      "Epoch 18/20\n",
      "60000/60000 [==============================] - 137s 2ms/step - loss: 0.0316 - acc: 0.9904 - val_loss: 0.0333 - val_acc: 0.9923\n",
      "Epoch 19/20\n",
      "60000/60000 [==============================] - 141s 2ms/step - loss: 0.0279 - acc: 0.9917 - val_loss: 0.0255 - val_acc: 0.9942\n",
      "Epoch 20/20\n",
      "60000/60000 [==============================] - 137s 2ms/step - loss: 0.0297 - acc: 0.9910 - val_loss: 0.0249 - val_acc: 0.9938\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10000/10000 [==============================] - 4s 377us/step\n",
      "\n",
      "Test accuracy: 99.4%\n"
     ]
    }
   ],
   "source": [
    "from __future__ import absolute_import\n",
    "from __future__ import division\n",
    "from __future__ import print_function\n",
    "\n",
    "import numpy as np\n",
    "\n",
    "from keras.layers import Dense, Dropout, Input\n",
    "from keras.layers import Conv2D, MaxPooling2D, Flatten\n",
    "from keras.models import Model\n",
    "from keras.layers.merge import concatenate\n",
    "from keras.datasets import mnist\n",
    "from keras.utils import to_categorical\n",
    "from keras.utils import plot_model\n",
    "\n",
    "# load MNIST dataset\n",
    "(x_train, y_train), (x_test, y_test) = mnist.load_data()\n",
    "\n",
    "# from sparse label to categorical\n",
    "num_labels = len(np.unique(y_train))\n",
    "y_train = to_categorical(y_train)\n",
    "y_test = to_categorical(y_test)\n",
    "\n",
    "# reshape and normalize input images\n",
    "image_size = x_train.shape[1]\n",
    "x_train = np.reshape(x_train,[-1, image_size, image_size, 1])\n",
    "x_test = np.reshape(x_test,[-1, image_size, image_size, 1])\n",
    "x_train = x_train.astype('float32') / 255\n",
    "x_test = x_test.astype('float32') / 255\n",
    "\n",
    "# network parameters\n",
    "input_shape = (image_size, image_size, 1)\n",
    "batch_size = 32\n",
    "kernel_size = 3\n",
    "dropout = 0.4\n",
    "n_filters = 32\n",
    "\n",
    "# left branch of Y network\n",
    "left_inputs = Input(shape=input_shape)\n",
    "x = left_inputs\n",
    "filters = n_filters\n",
    "# 3 layers of Conv2D-Dropout-MaxPooling2D\n",
    "# number of filters doubles after each layer (32-64-128)\n",
    "for i in range(3):\n",
    "    x = Conv2D(filters=filters,\n",
    "               kernel_size=kernel_size,\n",
    "               padding='same',\n",
    "               activation='relu')(x)\n",
    "    x = Dropout(dropout)(x)\n",
    "    x = MaxPooling2D()(x)\n",
    "    filters *= 2\n",
    "\n",
    "# right branch of Y network\n",
    "right_inputs = Input(shape=input_shape)\n",
    "y = right_inputs\n",
    "filters = n_filters\n",
    "# 3 layers of Conv2D-Dropout-MaxPooling2D\n",
    "# number of filters doubles after each layer (32-64-128)\n",
    "for i in range(3):\n",
    "    y = Conv2D(filters=filters,\n",
    "               kernel_size=kernel_size,\n",
    "               padding='same',\n",
    "               activation='relu',\n",
    "               dilation_rate=2)(y)\n",
    "    y = Dropout(dropout)(y)\n",
    "    y = MaxPooling2D()(y)\n",
    "    filters *= 2\n",
    "\n",
    "# merge left and right branches outputs\n",
    "y = concatenate([x, y])\n",
    "# feature maps to vector in preparation to connecting to Dense layer\n",
    "y = Flatten()(y)\n",
    "y = Dropout(dropout)(y)\n",
    "outputs = Dense(num_labels, activation='softmax')(y)\n",
    "\n",
    "# build the model in functional API\n",
    "model = Model([left_inputs, right_inputs], outputs)\n",
    "# verify the model using graph\n",
    "plot_model(model, to_file='cnn-y-network.png', show_shapes=True)\n",
    "# verify the model using layer text description\n",
    "model.summary()\n",
    "\n",
    "# classifier loss, Adam optimizer, classifier accuracy\n",
    "model.compile(loss='categorical_crossentropy',\n",
    "              optimizer='adam',\n",
    "              metrics=['accuracy'])\n",
    "\n",
    "# train the model with input images and labels\n",
    "model.fit([x_train, x_train],\n",
    "          y_train, \n",
    "          validation_data=([x_test, x_test], y_test),\n",
    "          epochs=20,\n",
    "          batch_size=batch_size)\n",
    "\n",
    "# model accuracy on test dataset\n",
    "score = model.evaluate([x_test, x_test], y_test, batch_size=batch_size)\n",
    "print(\"\\nTest accuracy: %.1f%%\" % (100.0 * score[1]))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
